#pragma once

#include <butil/string_printf.h>

#include "index/attribute_index/attribute_mem_indexer/attribute_mem_indexer.h"
#include "file_system/file_writer/normal_file_writer.h"
#include "util/radix_tree/typed_slice_list.h"

namespace hawking {
namespace indexlib {

template<class T>
class SingleValueAttributeMemIndexer : public AttributeMemIndexer {
public:
    static const uint32_t SLICE_LEN = 16 * 1024;
    static const uint32_t SLOT_NUM = 64;
    static constexpr T DEFAULT_VALUE = 0;

    SingleValueAttributeMemIndexer(int32_t field_id, util::ColumnType field_type) :
        AttributeMemIndexer(field_id, field_type) {}
    virtual ~SingleValueAttributeMemIndexer() = default;

    virtual void Init() override {
        BaseInit_();
        void* buffer = pool_->Allocate(sizeof(TypedSliceList<T>));
        data_ = new (buffer) TypedSliceList<T>(SLOT_NUM, SLICE_LEN, pool_.get());
    }

    virtual uint64_t DocCount() const override {
        return data_ ? data_->Size() : 0;
    }

    Status BatchRead(SegmentContext* segment_ctx);

    virtual Status Dump(
        const std::string& attr_dir,
        const std::unordered_map<DocId, DocId>& old2new_map) override;

    virtual uint32_t ElementLength() const override {
        return sizeof(T);
    }

protected:
    virtual Status AddField_(const std::string_view& value) override {
        if (!data_) {
            return Status::Uninitialize;
        }

        const T* val = reinterpret_cast<const T*>(value.data());
        data_->PushBack(*val);
        return Status::OK;
    }

    virtual util::HawkingRecallRetCode UpdateField_(
        DocId doc_id, const std::string_view& value) override {
        if (!data_) {
            return Status::Uninitialize;
        }

        data_->Upadte(doc_id, value);
        return Status::OK;
    }

    Status ReadOneDoc_(DocId doc_id, util::FieldValue* value) const;

private:
    TypedSliceList<T>* data_ = nullptr;
};

template<class T>
Status SingleValueAttributeMemIndexer<T>::ReadOneDoc_(
    DocId doc_id, util::FieldValue* value) const {
    T* buffer_ptr = nullptr;
    data_->Read(doc_id, buffer_ptr);
    if (!buffer_ptr) {
        return Status::NotFound;
    }

    switch (field_type_)
    {
    case util::ColumnType::COLUMN_INT8:
    case util::ColumnType::COLUMN_INT16:
    case util::ColumnType::COLUMN_INT32:
    case util::ColumnType::COLUMN_INT64:
        value->set_int_value(*buffer_ptr);
        return Status::OK;
    case util::ColumnType::COLUMN_FLOAT:
    case util::ColumnType::COLUMN_DOUBLE:
        value->set_double_value(*buffer_ptr);
        return Status::OK;
    default:
        break;
    }

    return Status::ConfigError;
}

template<class T>
Status SingleValueAttributeMemIndexer<T>::BatchRead(SegmentContext* segment_ctx) {
    std::vector<DocContext>& doc_ctxs = *(segment_ctx->MutableDocContext());
    for (size_t idx = 0; idx < segment_ctx->DocIds().size(); ++idx) {
        if (doc_ctxs[idx].filtered) {
            continue;
        }

        util::FieldValue* field_value =
            segment_ctx->ArenaCtx().CreateOnArena<util::FieldValue>();
        doc_ctxs[idx].field2attr.emplace(field_id_, field_value);

        Status ret = Read_(segment_ctx->DocIds()[idx], field_value);
        if (ret != Status::OK) {
            return ret;
        }
    }

    return Status::OK;
}

template<class T>
inline Status SingleValueAttributeMemIndexer<T>::Dump(
    const std::string& attr_dir,
    const std::unordered_map<DocId, DocId>& old2new_map) {
    std::string datafile_path = butil::string_printf("%s/data_file", attr_dir.data());
    std::unique_ptr<NormalFileWriter> data_file_writer =
        std::make_unique<NormalFileWriter>(datafile_path);
    Status ret = data_file_writer->Init();
    if (ret != Status::OK) {
        LOG(ERROR) << "dump attribute file " << datafile_path << " Init fail";
        return ret;
    }

    off_t offset = 0;
    for (uint64_t idx = 0; data_ && idx < data_->Size(); ++idx) {
        if (old2new_map.find(idx) == old2new_map.end()) {
            continue;
        }

        T* value = nullptr;
        data_->Read(idx, std::ref(value));
        ReturnValue<size_t> ret = data_file_writer->Write((uint8_t*)value, sizeof(T), offset);
        if (ret.Code() != Status::OK) {
            return ret.Code();
        }
        offset += sizeof(T);
    }

    data_file_writer->Close();
    return Status::OK;
}

}
}